`timescale 1 ns / 1 ps

module tb;
    import espi_pkg::*;

`include "test_lib.v"

    // 100 MHz
    localparam CLK_PERIOD = 10;

    localparam MAX_OP_FREQ = 3'b000;
    
    localparam STATUS_REG_OFFSET     = 5'h00;
    localparam CONTROL_REG_OFFSET    = 5'h01;
    localparam PC_RXFIFO_REG_OFFSET  = 5'h02;
    localparam PC_TXFIFO_REG_OFFSET  = 5'h03;
    localparam NP_RXFIFO_REG_OFFSET  = 5'h04;
    localparam NP_TXFIFO_REG_OFFSET  = 5'h05;
    localparam ERROR_REG_OFFSET      = 5'h06;
    localparam ISR_REG_OFFSET        = 5'h07;
    localparam OOB_RXFIFO_REG_OFFSET = 5'h08;
    localparam OOB_TXFIFO_REG_OFFSET = 5'h09;

    localparam OOB_MAX_PAYLOAD = 64;

`define wr_reg     avmm_write_reg
`define rd_reg     avmm_read_reg

    //`define SIMULATION

    reg [7:0]               bfm_byte;
    reg [7:0]               bfm_data[];
    reg [7:0]               bfm_data_init = 8'h0;
    reg [7:0]               bfm_status[0:1];
    reg [7:0]               bytes_avail = 8'h0;
    reg [31:0]              write_data = 32'hAAAA_5555;
    reg [31:0]              read_data;
    reg [31:0]              exp_data;
    reg [31:0]              act_data = 32'h0;
    reg [31:0]              tb_wr_config = 32'h0;
    reg [7:0]               vwire_status = 8'h0;
    reg [7:0]               byte_count = 8'h0;
    reg                     completed;
    reg                     vw_enabled = 1'b0;

    bit [1:0]               io_mode;
    bit [2:0]               op_freq;
    bit [3:0]               max_wait;

    parameter DEVICE_FAMILY              = "MAX 10 FPGA";
    parameter IO_MODE_RANGE              = 2'b11;
    parameter OPEN_DRAIN_ALERT_EN        = 1'b0;
    parameter MAX_FREQ_RANGE             = 3'b000;
    parameter CHANNEL_SUPPORT            = 8'b00000111;
    parameter MAX_PC_PAYLOAD_SIZE_RANGE  = 3'b001;
    parameter MAX_PC_READREQ_SIZE_RANGE  = 3'b001;
    parameter MAX_OOB_PAYLOAD_SIZE_RANGE = 3'b001;
    parameter MAX_VW_COUNT_SUPPORTED = 6'b000111;

    localparam int PC_MAX_READREQ_SIZE = 64;
    localparam int PC_MAX_PAYLOAD_SIZE = 64;

    logic         clk;
    logic         reset_n;
    logic         avmm_write;
    logic         avmm_read;
    logic [31:0]  avmm_writedata;
    logic [4:0]   avmm_address;
    logic [31:0]  avmm_readdata;
    logic         avmm_readdatavalid;
    // 
    // eSPI Memory Read/Write interface (Avalon Memory Mapped Master)
    logic         mem_reset;
    logic [31:0]  mem_address;
    logic         mem_write;
    logic [31:0]  mem_writedata;
    logic [3:0]   mem_byteenable;
    logic         mem_read;
    logic [31:0]  mem_readdata;
    logic         mem_waitrequest;
    // 
    // eSPI I/O Read/Write Short interface (Avalon Memory Mapped Master)
    logic         io_reset;
    logic [15:0]  io_address;
    logic         io_write;
    logic [7:0]   io_writedata;
    logic         io_read;
    logic [7:0]   io_readdata;
    logic         io_waitrequest;

    // interrupt
    logic         irq;
    logic         oob_rxfifo_avail;

    // espi protocol
    logic         espi_clk;
    logic         espi_reset_n;
    logic         espi_cs_n;
    logic [3:0]   espi_data_in;
    logic [3:0]   espi_data_out;
    logic         espi_alert_n;
    tri [3:0]     espi_io;

    // Virtual Wire external inputs & outputs
    logic [255:0] vw_irq; // Interrupt requests
    logic [255:0] vw_plti; // Platform-specific system inputs
    logic [255:0] vw_plto; // Platform-specific system outputs
    logic [511:0] vw_gpi; // General-Purpose inputs
    logic [511:0] vw_gpo; // General-Purpose outputs

    // Virtual Wire Outputs
    logic         slp_s3_n;
    logic         slp_s4_n;
    logic         slp_s5_n;
    logic         sus_stat_n;
    logic         pltrst_n;
    logic         oob_rst_warn;
    logic         host_rst_warn;
    logic         smiout_n;
    logic         nmiout_n;

    // Virtual Wire Inputs
    logic         slave_boot_load_done;
    logic         slave_boot_load_status;
    logic         oob_rst_ack;
    logic         wake_n;
    logic         pme_n;
    logic         sci_n;
    logic         smi_n;
    logic         rcin_n;
    logic         host_rst_ack;
    logic         rsmrst_n;
    logic         error_nonfatal;
    logic         error_fatal;

    // output FPGA_ESPI_BASE read only registers for access through avmm i/f
    logic [31:0]  device_id_reg;
    logic [31:0]  general_config_reg;
    logic [31:0]  channel0_config_reg;
    logic [31:0]  channel1_config_reg;
    logic [31:0]  channel2_config_reg;
    logic [31:0]  channel3_config_reg;


    espi_slave
      #(
        .DEVICE_FAMILY              (DEVICE_FAMILY),
        .IO_MODE_RANGE              (IO_MODE_RANGE),
        .OPEN_DRAIN_ALERT_EN        (OPEN_DRAIN_ALERT_EN),
        .MAX_FREQ_RANGE             (MAX_FREQ_RANGE),
        .CHANNEL_SUPPORT            (CHANNEL_SUPPORT),
        .MAX_PC_PAYLOAD_SIZE_RANGE  (MAX_PC_PAYLOAD_SIZE_RANGE),
        .MAX_PC_READREQ_SIZE_RANGE  (MAX_PC_READREQ_SIZE_RANGE),
        .MAX_OOB_PAYLOAD_SIZE_RANGE (MAX_OOB_PAYLOAD_SIZE_RANGE),
        .MAX_VW_COUNT_SUPPORTED     (MAX_VW_COUNT_SUPPORTED)
        )
    DUT
      (
       .clk                    (clk),
       .reset_n                (reset_n),
       .avmm_write             (avmm_write),
       .avmm_read              (avmm_read),
       .avmm_writedata         (avmm_writedata),
       .avmm_address           (avmm_address),
       .avmm_readdata          (avmm_readdata),
       .avmm_readdatavalid     (avmm_readdatavalid),
       .mem_reset              (mem_reset),
       .mem_address            (mem_address),
       .mem_write              (mem_write),
       .mem_writedata          (mem_writedata),
       .mem_byteenable         (mem_byteenable),
       .mem_read               (mem_read),
       .mem_readdata           (mem_readdata),
       .mem_waitrequest        (mem_waitrequest),
       .io_reset               (io_reset),
       .io_address             (io_address),
       .io_write               (io_write),
       .io_writedata           (io_writedata),
       .io_read                (io_read),
       .io_readdata            (io_readdata),
       .io_waitrequest         (io_waitrequest),
       .irq                    (irq),
       .oob_rxfifo_avail       (oob_rxfifo_avail),
       .espi_clk               (espi_clk),
       .espi_reset_n           (espi_reset_n),
       .espi_cs_n              (espi_cs_n),
       .espi_data_in           (espi_data_in),
       .espi_data_out          (espi_data_out),
       .espi_alert_n           (espi_alert_n),
       .vw_irq                 (vw_irq),
       .vw_plti                (vw_plti),
       .vw_plto                (vw_plto),
       .vw_gpi                 (vw_gpi),
       .vw_gpo                 (vw_gpo),
       .slp_s3_n               (slp_s3_n),
       .slp_s4_n               (slp_s4_n),
       .slp_s5_n               (slp_s5_n),
       .sus_stat_n             (sus_stat_n),
       .pltrst_n               (pltrst_n),
       .oob_rst_warn           (oob_rst_warn),
       .host_rst_warn          (host_rst_warn),
       .smiout_n               (smiout_n),
       .nmiout_n               (nmiout_n),
       .slave_boot_load_done   (slave_boot_load_done),
       .slave_boot_load_status (slave_boot_load_status),
       .oob_rst_ack            (oob_rst_ack),
       .wake_n                 (wake_n),
       .pme_n                  (pme_n),
       .sci_n                  (sci_n),
       .smi_n                  (smi_n),
       .rcin_n                 (rcin_n),
       .host_rst_ack           (host_rst_ack),
       .rsmrst_n               (rsmrst_n),
       .error_nonfatal         (error_nonfatal),
       .error_fatal            (error_fatal),
       .device_id_reg          (device_id_reg),
       .general_config_reg     (general_config_reg),
       .channel0_config_reg    (channel0_config_reg),
       .channel1_config_reg    (channel1_config_reg),
       .channel2_config_reg    (channel2_config_reg),
       .channel3_config_reg    (channel3_config_reg)
       );

    // Test stimulus
    initial begin
        espi_master_bfm.verbose = 1;
        espi_master_bfm.debug = 1;
        start_test_suite("Library:  ESPI I/F Test Suite");
        for (op_freq = 0; op_freq <= MAX_OP_FREQ; op_freq++) begin
            for (io_mode = 0; io_mode < 3; io_mode++) begin
                max_wait = $urandom();
                tb_reset();
                statusf("Run tests with I/O Mode = %1d, Op. Freq. = %1d, Max Wait State = %1d",
                        {argf(io_mode), argf(op_freq), argf(max_wait)});
                `runtest("put_oob_test",  put_oob_test);
                `runtest("get_oob_test",  get_oob_test);
                `runtest("vw_irq_test", vw_irq_test);
                `runtest("vw_plti_test", vw_plti_test);
                `runtest("vw_gpi_test", vw_gpi_test);
                `runtest("vw_plto_test", vw_plto_test);
                `runtest("vw_gpo_test", vw_gpo_test);
                `runtest("put_iowr_short_test", put_iowr_short_test);
                `runtest("put_iord_short_test", put_iord_short_test);
                `runtest("put_memwr_short_test", put_memwr_short_test);
                `runtest("put_memrd_short_test", put_memrd_short_test);
                `runtest("put_pc_memwr_test", put_pc_memwr_test);
                `runtest("put_np_memrd_test", put_np_memrd_test);
                `runtest("random_traffic_test", random_traffic_test);
            end // for (io_mode = 0; io_mode < 3; io_mode++)
        end // for (op_freq = 0; op_freq <= MAX_OP_FREQ; op_freq++)
        end_test_suite ();
        $stop;
    end // initial begin

    // initial blocks
    initial begin
        clk = 0;
        #13;
        forever begin
            #(CLK_PERIOD/2) clk = ~clk;
        end
    end

    initial begin
        slave_boot_load_done   = 0;
        slave_boot_load_status = 0;
        oob_rst_ack            = 0;
        wake_n                 = 1;
        pme_n                  = 1;
        sci_n                  = 1;
        smi_n                  = 1;
        rcin_n                 = 1;
        host_rst_ack           = 0;
        rsmrst_n               = 1;
        error_nonfatal         = 0;
        error_fatal            = 0;
        vw_irq                 = 0;
        vw_plti                = 0;
        vw_gpi                 = 0;
        reset_n                = 0;
        repeat (10) @(posedge clk);

        reset_n = 1;
        @(posedge clk);
    end // initial begin

    // Initialize buffers for espi tests
    initial begin
        bfm_status[0] = 8'b0;
        bfm_status[1] = 8'b0;
    end

    reg [7:0] get_vw_return_data[];

    initial begin
        avmm_address    = 0;
        avmm_writedata  = 0;
        avmm_read       = 0;
        avmm_write      = 0;
    end

    espi_master_bfm espi_master_bfm
      (
       .espi_clk     (espi_clk    ),
       .espi_cs_n    (espi_cs_n   ),
       .espi_reset_n (espi_reset_n),
       .espi_io      (espi_io     ),
       .espi_alert_n (espi_alert_n)
       );

    assign espi_io      = espi_reset_n ? espi_data_out : 4'bzzzz;
    assign espi_data_in = espi_io;

    pullup(espi_io[0]);
    pullup(espi_io[1]);
    pullup(espi_io[2]);
    pullup(espi_io[3]);


    task put_oob_test();
        logic [7:0]  data[];
        logic [7:0]  rdata;
        logic [11:0] length;

        start_test("PUT_OOB Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        for (int trial = 1; trial <= 10; trial++) begin
            put_oob_trial();
        end
        end_test();
    endtask // oob_test


    task get_oob_test();
        start_test("GET_OOB Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        for (int trial = 1; trial <= 10; trial++) begin
            get_oob_trial();
        end
        end_test();
    endtask // oob_test


    task vw_irq_test();
        logic [7:0] rand_irq;
        logic [7:0] irq[];
        int         n;
        start_test("VW IRQ Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single interrupts.");
        vw_irq_check_single(0, 1);
        vw_irq_check_single(0, 0);
        vw_irq_check_single(255, 1);
        vw_irq_check_single(255, 0);
        rand_irq = $urandom_range(2, 253);
        for (int i=-1; i<2; i++) begin
            vw_irq_check_single(rand_irq+i, 1);
            vw_irq_check_single(rand_irq+i, 0);
        end

        for (int tries = 1; tries <= 10; tries++) begin
            n   = $urandom_range(1,8);
            irq = new[n];
            for (int i = 0; i < n; i++) begin
                irq[i] = $urandom_range(0, 255);
            end
            vw_irq_check_multi(irq, 1);
            vw_irq_check_multi(irq, 0);
        end
        end_test();
    endtask // vw_irq_test


    task vw_plti_test();
        logic [7:0] rand_plti;
        logic [7:0] plti[];
        int         n;
        start_test("VW Platform Input Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single platform inputs.");
        vw_plti_check_single(0, 1);
        vw_plti_check_single(0, 0);
        vw_plti_check_single(255, 1);
        vw_plti_check_single(255, 0);
        for (int trials=0; trials<10; trials++) begin
            rand_plti = $urandom_range(3, 252);
            for (int i=-2; i<3; i++) begin
                vw_plti_check_single(rand_plti+i, 1);
                vw_plti_check_single(rand_plti+i, 0);
            end
        end

        for (int tries = 1; tries <= 10; tries++) begin
            n    = $urandom_range(1,32);
            plti = new[n];
            for (int i = 0; i < n; i++) begin
                plti[i] = $urandom_range(0, 255);
            end
            vw_plti_check_multi(plti, 1);
            vw_plti_check_multi(plti, 0);
        end
        end_test();
    endtask // vw_plti_test


    task vw_gpi_test();
        logic [8:0] rand_gpi;
        logic [7:0] gpi[];
        int         n;
        start_test("VW General Purpose Input Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single general purpose inputs.");
        vw_gpi_check_single(0, 1);
        vw_gpi_check_single(0, 0);
        vw_gpi_check_single(511, 1);
        vw_gpi_check_single(511, 0);
        for (int trials=0; trials<10; trials++) begin
            rand_gpi = $urandom_range(3, 511-3);
            for (int i=-2; i<3; i++) begin
                vw_gpi_check_single(rand_gpi+i, 1);
                vw_gpi_check_single(rand_gpi+i, 0);
            end
        end

        for (int tries = 1; tries <= 10; tries++) begin
            n    = $urandom_range(1,32);
            gpi = new[n];
            for (int i = 0; i < n; i++) begin
                gpi[i] = $urandom_range(0, 255);
            end
            vw_gpi_check_multi(gpi, 1);
            vw_gpi_check_multi(gpi, 0);
        end
        end_test();
    endtask // vw_gpi_test


    task vw_plto_test();
        logic [7:0] rand_plto;
        start_test("VW Platform Output Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single platform outputs.");
        vw_plto_check_single(0, 1);
        vw_plto_check_single(0, 0);
        vw_plto_check_single(255, 1);
        vw_plto_check_single(255, 0);
        for (int trials=0; trials<10; trials++) begin
            rand_plto = $urandom_range(3, 252);
            for (int i=-2; i<3; i++) begin
                vw_plto_check_single(rand_plto+i, 1);
                vw_plto_check_single(rand_plto+i, 0);
            end
        end
        end_test();
    endtask // vw_plto_test


    task vw_gpo_test();
        logic [8:0] rand_gpo;
        start_test("VW General Purpose Output Test");
        tb_reset();
        espi_config();
        status("Verify the assertion and deassertion of single general purpose outputs.");
        vw_gpo_check_single(0, 1);
        vw_gpo_check_single(0, 0);
        vw_gpo_check_single(255, 1);
        vw_gpo_check_single(255, 0);
        for (int trials=0; trials<10; trials++) begin
            rand_gpo = $urandom_range(3, 511-3);
            for (int i=-2; i<3; i++) begin
                vw_gpo_check_single(rand_gpo+i, 1);
                vw_gpo_check_single(rand_gpo+i, 0);
            end
        end
        end_test();
    endtask // vw_gpo_test


    task put_iowr_short_test();
        start_test("PUT_IOWR_SHORT Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        espi_master_bfm.tb_response = DEFER;
        for (int trial = 1; trial <= 10; trial++) begin
            put_iowr_short_trial();
        end // for (int trial = 1; trial <= 10; trial++)
        end_test();
    endtask // put_iowr_short_test

    task put_iord_short_test();
        start_test("PUT_IORD_SHORT Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        espi_master_bfm.tb_response  = DEFER;
        for (int trial = 1; trial <= 10; trial++) begin
            put_iord_short_trial();
        end // for (int trial = 1; trial <= 10; trial++)
        end_test();
    endtask // put_iord_short_test
    

    task put_memwr_short_test();
        start_test("PUT_MEMWR32_SHORT Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        for (int trial = 1; trial <= 10; trial++) begin
            put_memwr_short_trial();
        end // for (int trial = 1; trial <= 10; trial++)
        end_test();
    endtask // put_memwr_short_test


    task put_memrd_short_test();
        start_test("PUT_MEMRD32_SHORT Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        espi_master_bfm.tb_response  = DEFER;
        for (int trial = 1; trial <= 10; trial++) begin
            put_memrd_short_trial();
        end // for (int trial = 1; trial <= 10; trial++)
        end_test();
    endtask // put_memrd_short_test
    

    task put_pc_memwr_test();
        start_test("PUT_PC_MEMWR32 Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        for (int trial = 1; trial <= 10; trial++) begin
            put_pc_memwr_trial();
        end // for (int trial = 1; trial <= 10; trial++)
        end_test();
    endtask // put_pc_memwr_test
    

    task put_np_memrd_test();
        start_test("PUT_NP_MEMRD32 Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        for (int trial = 1; trial <= 10; trial++) begin
            put_np_memrd_trial();
        end // for (int trial = 1; trial <= 10; trial++)
        end_test();
    endtask // put_np_memrd_test
    

    task random_traffic_test();
        int op;
        
        start_test("Random Traffic Test");
        tb_reset();
        espi_config();
        deassert_pltrst_n();
        for (int trial = 1; trial <= 10; trial++) begin
            op = $urandom_range(0,7);
            case (op)
              0: put_oob_trial();
              1: get_oob_trial();
              2: put_iowr_short_trial();
              3: put_iord_short_trial();
              4: put_memwr_short_trial();
              5: put_memrd_short_trial();
              6: put_pc_memwr_trial();
              7: put_np_memrd_trial();
              default: ;
            endcase // case (op)
        end // for (int trial = 1; trial <= 10; trial++)
        end_test();
    endtask // random_traffic_test

    
    task put_oob_trial();
        logic [7:0] data[];
        logic [7:0] rdata;
        int         n;

        status("Sending PUT_OOB message");
        n    = $urandom_range(4, OOB_MAX_PAYLOAD+3);
        data = new[n];
        for (int i = 0; i < n; i++) begin
            if (i == 2) begin
                data[i] = n-3;
            end
            else begin
                data[i] = $urandom();
            end
        end

        espi_master_bfm.put_oob(data);
        fork
            begin
                status("Waiting for OOB Available");
                wait (oob_rxfifo_avail);
                status("OOB Available asserted");
            end

            begin
                #1e5;
                status ("OOB Available timeout");
            end
        join_any

        disable fork;
        status("Verifying received OOB data");
        compare(1, oob_rxfifo_avail, "OOB AVAIL");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(PUT_OOB, rdata, "COMMAND");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(OOB_MESSAGE, rdata, "CYCLETYPE");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(0, rdata, "TAG,LENGTH_HI");
        `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
        compare(n, rdata, "LENGTH_LO");
        if (oob_rxfifo_avail) begin
            for (int i = 0; i < n; i++) begin
                @(posedge clk);
                `rd_reg(OOB_RXFIFO_REG_OFFSET, rdata);
                compare(data[i], rdata, "OOB Rx FIFO");
            end
        end
    endtask // put_oob_trial
    

    task get_oob_trial();
        logic [7:0] data[];
        logic [7:0] oob_data[];
        int         n;

        status("Writing a message to the OOB Tx FIFO");
        n       = $urandom_range(7,OOB_MAX_PAYLOAD+3+3);
        data    = new[n];
        data[0] = OOB_MESSAGE;
        data[1] = 0;
        data[2] = n-3;
        for (int i = 3; i < n; i++) begin
            if (i == 5) begin
                data[i] = n-6;
            end
            else begin
                data[i] = $urandom();
            end
        end

        for (int i = 0; i < n; i++) begin
            `wr_reg(OOB_TXFIFO_REG_OFFSET, data[i]);
        end

        status("Indicate the OOB TX FIFO has a message to send");
        `wr_reg(CONTROL_REG_OFFSET, 32'h04);

        status("Wait for ALERT# to assert");
        fork
            wait (!espi_alert_n);
            #1e6;
        join_any
        disable fork;

        compare(0, espi_alert_n, "ALERT#");
        status("Read and verify that STATUS indicates an OOB message available");
        espi_master_bfm.get_status(bfm_status);
        compare(1, espi_alert_n, "ALERT#");
        compare('h8f, bfm_status[0], "ESPI STATUS[7:0]");
        compare('h03, bfm_status[1], "ESPI STATUS[15:0]");
        status("Send GET_OOB message");
        espi_master_bfm.get_oob(oob_data);
        status("Verify received data is correct");
        compare(n, data.size(), "OOB Data Bytes");
        for (int i = 0; i < n-3; i++) begin
            compare(data[i+3], oob_data[i], "OOB Data");
        end
    endtask // get_oob_trial


    task automatic vw_irq_check_single(input [7:0] irq, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_irq[irq];

        status("Checking VWIRE IRQ");
        $display("\tSetting IRQ %1d to %b", irq, state);
        vw_irq[irq] = state;
        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        disable fork;
        compare(state == prev_state, espi_alert_n, "eSPI ALERT#");
        if (!espi_alert_n) begin
            status("Verifying VWIRE content");
            espi_master_bfm.get_vwire(data);
            compare(2, data.size(), "GET_VWIRE bytes");
            compare(irq/128, data[0], "VWIRE Index");
            compare(irq%128|(state << 7), data[1], "VWIRE IRQ and state");
        end
    endtask // vw_irq_check_single

    
    task automatic vw_irq_check_multi(input [7:0] irq[], input state);
        automatic logic [7:0] data[];
        automatic int matched;

        statusf("Checking %1d VWIRE IRQs", {argf(irq.size())});
        for (int i = 0; i < irq.size(); i++) begin
            vw_irq[irq[i]] = state;
        end
        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        compare(0, espi_alert_n, "eSPI ALERT#");
        disable fork;
        matched = 0;
        while (!espi_alert_n) begin
            status("Verifying VWIRE content");
            espi_master_bfm.get_status(bfm_status);
            compare(1, bfm_status[0][6], "VWIRE_AVAIL");
            while (bfm_status[0][6]) begin
                espi_master_bfm.get_vwire(data);
                for (int i = 0; i < data.size(); i += 2) begin
                    for (int j = 0; j < irq.size(); j++) begin
                        if (data[i] == irq[j][7] && data[i+1][6:0] == irq[j][6:0]) begin
                            matched++;
                            compare(vw_irq[irq[j]], data[i+1][7], "VWIRE IRQ state");
                        end
                    end
                end
                espi_master_bfm.get_status(bfm_status);
            end // if (bfm_status[0][6])
        end // while (!espi_alert_n)
        compare(irq.size(), matched, "Number of matches");
    endtask // vw_irq_check_multi

    
    task automatic vw_plti_check_single(input [7:0] plti, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_plti[plti];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [1:0] pos;
        status("Checking VWIRE Platform Input");
        $display("\tSetting Platform Input %1d to %b", plti, state);
        vw_plti[plti] = state;
        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        disable fork;
        compare(state == prev_state, espi_alert_n, "eSPI ALERT#");
        if (!espi_alert_n) begin
            status("Verifying VWIRE content");
            espi_master_bfm.get_vwire(data);
            index  = plti/4+64;
            pos    = plti%4;
            valid  = 1 << pos;
            value  = state << pos;
            compare(2, data.size(), "GET_VWIRE bytes");
            compare(index, data[0], "VWIRE Index");
            compare({valid, value}, data[1], "VWIRE Valid and Value");
        end // if (!espi_alert_n)
    endtask // vw_plti_check_single

    
    task automatic vw_plti_check_multi(input [7:0] plti[], input state);
        automatic logic [7:0] data[];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [3:0] exp_valid;
        automatic logic [3:0] exp_value;
        automatic logic [255:0] updated_vw_plti;
        int n_exp;
        int n_act;
        status("Checking VWIRE Platform Input");
        updated_vw_plti = vw_plti;
        for (int i = 0; i < plti.size(); i++) begin
            vw_plti[plti[i]] = state;
        end

        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        compare(0, espi_alert_n, "eSPI ALERT#");
        disable fork;
        while (!espi_alert_n) begin
            status("Verifying VWIRE content");
            espi_master_bfm.get_status(bfm_status);
            compare(1, bfm_status[0][6], "VWIRE_AVAIL");
            while (bfm_status[0][6]) begin
                espi_master_bfm.get_vwire(data);
                for (int i = 0; i < data.size(); i += 2) begin
                    index     = data[i]*4;
                    valid     = data[i+1][7:4];
                    value     = data[i+1][3:0];
                    exp_valid = vw_plti[index+:4] ^ updated_vw_plti[index+:4];
                    exp_value = vw_plti[index+:4];
                    compare(exp_valid, valid, "VWIRE VALID");
                    compare(exp_value, value, "VWIRE VALUE");
                    updated_vw_plti[index+:4] = exp_value;
                end
                espi_master_bfm.get_status(bfm_status);
            end
        end // if (!espi_alert_n)
        n_exp = $countones(vw_plti);
        n_act = $countones(updated_vw_plti);
        compare(n_exp, n_act, "Platform Input 1's");
    endtask // vw_plti_check_multi

    
    task automatic vw_gpi_check_single(input [8:0] gpi, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_gpi[gpi];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [1:0] pos;
        status("Checking VWIRE General Purpose Input");
        $display("\tSetting General Purpose Input %1d to %b", gpi, state);
        vw_gpi[gpi] = state;
        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        disable fork;
        compare(state == prev_state, espi_alert_n, "eSPI ALERT#");
        if (!espi_alert_n) begin
            status("Verifying VWIRE content");
            espi_master_bfm.get_vwire(data);
            index  = gpi/4+128;
            pos    = gpi%4;
            valid  = 1 << pos;
            value  = state << pos;
            compare(2, data.size(), "GET_VWIRE bytes");
            compare(index, data[0], "VWIRE Index");
            compare({valid, value}, data[1], "VWIRE Valid and Value");
        end // if (!espi_alert_n)
    endtask // vw_gpi_check_single

    
    task automatic vw_gpi_check_multi(input [7:0] gpi[], input state);
        automatic logic [7:0] data[];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [3:0] exp_valid;
        automatic logic [3:0] exp_value;
        automatic logic [511:0] updated_vw_gpi;
        int n_exp;
        int n_act;
        status("Checking VWIRE Platform Input");
        updated_vw_gpi = vw_gpi;
        for (int i = 0; i < gpi.size(); i++) begin
            vw_gpi[gpi[i]] = state;
        end

        fork
            wait (!espi_alert_n);
            #1e5;
        join_any
        compare(0, espi_alert_n, "eSPI ALERT#");
        disable fork;
        while (!espi_alert_n) begin
            status("Verifying VWIRE content");
            espi_master_bfm.get_status(bfm_status);
            compare(1, bfm_status[0][6], "VWIRE_AVAIL");
            while (bfm_status[0][6]) begin
                espi_master_bfm.get_vwire(data);
                for (int i = 0; i < data.size(); i += 2) begin
                    index     = data[i]*4;
                    valid     = data[i+1][7:4];
                    value     = data[i+1][3:0];
                    exp_valid = vw_gpi[index+:4] ^ updated_vw_gpi[index+:4];
                    exp_value = vw_gpi[index+:4];
                    compare(exp_valid, valid, "VWIRE VALID");
                    compare(exp_value, value, "VWIRE VALUE");
                    updated_vw_gpi[index+:4] = exp_value;
                end
                espi_master_bfm.get_status(bfm_status);
            end
        end // if (!espi_alert_n)
        n_exp = $countones(vw_gpi);
        n_act = $countones(updated_vw_gpi);
        compare(n_exp, n_act, "Platform Input 1's");
    endtask // vw_gpi_check_multi

    
    task automatic vw_plto_check_single(input [7:0] plto, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_plto[plto];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [1:0] pos;
        status("Checking VWIRE Platform Output");
        $display("\tSetting Platform Onput %1d to %b", plto, state);
        data    = new[2];
        index   = plto/4+64;
        pos     = plto%4;
        valid   = 1 << pos;
        value   = state << pos;
        data[0] = index;
        data[1] = {valid, value};
        espi_master_bfm.put_vwire(data);
        fork
            wait (vw_plto[plto] == state);
            #1e5;
        join_any
        disable fork;
        compare(state, vw_plto[plto], "VWIRE Platform Output");
    endtask // vw_plto_check_single

    
    task automatic vw_gpo_check_single(input [8:0] gpo, input state);
        automatic logic [7:0] data[];
        automatic logic       prev_state = vw_gpo[gpo];
        automatic logic [7:0] index;
        automatic logic [3:0] valid;
        automatic logic [3:0] value;
        automatic logic [1:0] pos;
        status("Checking VWIRE General Purpose Output");
        $display("\tSetting General Purpose Onput %1d to %b", gpo, state);
        data    = new[2];
        index   = gpo/4+128;
        pos     = gpo%4;
        valid   = 1 << pos;
        value   = state << pos;
        data[0] = index;
        data[1] = {valid, value};
        espi_master_bfm.put_vwire(data);
        fork
            wait (vw_gpo[gpo] == state);
            #1e5;
        join_any
        disable fork;
        compare(state, vw_gpo[gpo], "VWIRE General Purpose Output");
    endtask // vw_gpo_check_single


    task put_iowr_short_trial();
        logic [7:0]  data[];
        logic [15:0] addr;
        int          n;
        logic        completed;
        logic [7:0]  np_cycle_type;
        logic [3:0]  np_tag;
        logic [11:0] np_len;
        logic [63:0] np_addr;
        logic [7:0]  np_data[];
        int          tries;

        addr = $urandom()%2**16;
        n    = $urandom_range(1,3);
        n    = n == 3 ? 4 : n;
        statusf("Writing %1d bytes to I/O address %04h", {argf(n), argf(addr)});
        data = new[n];
        for (int i = 0; i < n; i++) begin
            data[i] = $urandom()%2**8;
        end
        fork
            begin
                espi_master_bfm.put_iowr_short(completed, addr, data);
                if (!completed) begin
                    wait (!espi_alert_n);
                    tries = 0;
                    do begin
                        espi_master_bfm.get_status(bfm_status);
                    end
                    while (!bfm_status[0][PC_AVAIL] && tries++ < 10); // PC_AVAIL
                    compare(16'h031f, {bfm_status[1], bfm_status[0]}, "eSPI STATUS");
                    espi_master_bfm.get_pc(np_cycle_type, np_tag, np_len, np_addr, np_data);
                    compare(SUCCESSFUL_COMPLETE_NO_DATA, np_cycle_type, "CYCLE_TYPE");
                    compare(0, np_tag, "TAG");
                    compare(n, np_len, "LENGTH");
                end // if (!completed)
                check_espi_status(16'h030f);
            end // fork begin
            fork
                begin
                    for (int i = 0; i < n; i++) begin
                        wait(io_write);
                        for (int waits = $urandom_range(0,7<<io_mode); waits > 0; waits--) begin
                            io_waitrequest = 1;
                            @(posedge clk);
                        end

                        io_waitrequest = 0;
                        @(posedge clk);
                        status("Verifying I/O WRITE operation");
                        compare((addr+i)&16'hffff, io_address, "I/O Address");
                        compare(data[i], io_writedata, "I/O Write Data");
                    end // for (int i = 0; i < n; i++)
                end // fork begin
                begin
                    #1e6;
                    local_error_cnt++;
                    status("I/O Write TIMEOUT");
                end
            join_any
        join
        disable fork;
    endtask // put_iowr_short_trial
    

    task put_iord_short_trial();
        logic [7:0]  data[];
        logic [7:0]  rdata[];
        logic [15:0] addr;
        int          n;
        logic [7:0]  np_cycle_type;
        logic [3:0]  np_tag;
        logic [11:0] np_len;
        logic [63:0] np_addr;
        logic [7:0]  np_data[];
        int          tries;

        addr = $urandom()%2**16;
        n    = $urandom_range(1,3);
        n    = n == 3 ? 4 : n;
        statusf("Reading %1d bytes from I/O address %04h", {argf(n), argf(addr)});
        data  = new[n];
        rdata = new[n];
        for (int i = 0; i < n; i++) begin
            data[i] = $urandom()%2**8;
        end
        fork
            begin
                espi_master_bfm.put_iord_short(completed, rdata, addr);
                if (!completed) begin
                    if (!espi_master_bfm.espi_status[PC_AVAIL]) begin
                        wait (!espi_alert_n);
                        tries = 0;
                        do begin
                            espi_master_bfm.get_status(bfm_status);
                        end
                        while (!bfm_status[0][PC_AVAIL] && tries++ < 10); // PC_AVAIL
                    end
                    compare(16'h031f, {bfm_status[1], bfm_status[0]}, "eSPI STATUS");
                    espi_master_bfm.get_pc(np_cycle_type, np_tag, np_len, np_addr, np_data);
                    status("Verifying I/O READ completion header");
                    compare(SUCCESSFUL_COMPLETE_DATA_SO, np_cycle_type, "CYCLETYPE");
                    compare(0, np_tag, "TAG");
                    compare(n, np_len, "LENGTH");
                    rdata = np_data;
                end // if (!completed)
                check_espi_status(16'h030f);
            end // fork begin
            fork
                begin
                    for (int i = 0; i < n; i++) begin
                        wait(io_read);
                        for (int waits = $urandom_range(0,15<<io_mode); waits > 0; waits--) begin
                            io_waitrequest = 1;
                            @(posedge clk);
                        end

                        io_waitrequest = 0;
                        io_readdata    = data[i];
                        @(posedge clk);

                        status("Verifying I/O READ operation");
                        compare(addr + i, io_address, "I/O Address");
                    end // for (int i = 0; i < n; i++)
                end // fork begin
                begin
                    #1e6;
                    local_error_cnt++;
                    status("I/O Read TIMEOUT");
                end
            join_any
        join

        disable fork;
        status("Verifying I/O READ data");
        for (int i = 0; i < n; i++) begin
            compare(data[i], rdata[i], "I/O Read Data");
        end
    endtask // put_iord_short_trial


    task put_memwr_short_trial();
        logic [31:0] addr;
        logic [7:0]  data[];
        logic [3:0]  byteenable;
        logic [31:0] writedata;
        int          n;

        n    = $urandom_range(1,3);
        n    = n == 3 ? 4 : n;
        addr = $urandom();
        if (n == 2) begin
            addr[0] = 0;
        end
        else if (n == 4) begin
            addr[1:0] = 0;
        end
        statusf("Writing %1d bytes to Memory address %08x", {argf(n), argf(addr)});
        data = new[n];
        for (int i = 0; i < n; i++) begin
            data[i] = $urandom()%2**8;
        end
        writedata = 0;
        case (n)
          1: begin
              writedata[addr[1:0]*8+:8] = data[0];
              byteenable                = 1'b1 << addr[1:0];
          end
          2: begin
              writedata[addr[1]*16+:16] = {data[1], data[0]};
              byteenable                = 2'b11 << addr[1:0];
          end
          4: begin
              writedata  = {data[3], data[2], data[1], data[0]};
              byteenable = 4'b1111;
          end
        endcase // case (n)
        fork
            begin
                espi_master_bfm.put_memwr_short(addr, data);
                check_espi_status(16'h030f);
            end
            fork
                begin
                    wait(mem_write);
                    @(posedge clk);
                    status("Verifying I/O WRITE operation");
                    compare({addr[31:2], 2'b0}, mem_address, "Memory Address");
                    compare(byteenable, mem_byteenable, "Memory Write Byte Enable");
                    compare(writedata, mem_writedata, "Memory Write Data");
                end
                begin
                    #1e6;
                    local_error_cnt++;
                    status("Memory Write TIMEOUT");
                end
            join_any
        join
        disable fork;
    endtask // put_memwr_short_trial
    

    task put_memrd_short_trial();
        logic [7:0]  data[];
        logic [7:0]  rdata[];
        logic [31:0] addr;
        int          n;
        logic [7:0]  np_cycle_type;
        logic [3:0]  np_tag;
        logic [11:0] np_len;
        logic [63:0] np_addr;
        logic [7:0]  np_data[];
        int          b;
        int          tries;

        n    = $urandom_range(1,3);
        n    = n == 3 ? 4 : n;
        addr = $urandom();
        if (n == 4) begin
            addr[1:0] = 0;
        end
        else if (n == 2) begin
            addr[0] = 0;
        end
        statusf("Reading %1d bytes from Memory address %08x", {argf(n), argf(addr)});
        data  = new[n];
        rdata = new[n];
        for (int i = 0; i < n; i++) begin
            data[i] = $urandom();
        end
        fork
            begin
                espi_master_bfm.put_memrd_short(completed, rdata, addr);
                if (!completed) begin
                    wait (!espi_alert_n);
                    tries = 0;
                    do begin
                        espi_master_bfm.get_status(bfm_status);
                    end
                    while (!bfm_status[0][PC_AVAIL] && tries++ < 10); // PC_AVAIL
                    compare(16'h031f, {bfm_status[1], bfm_status[0]}, "eSPI STATUS");
                    espi_master_bfm.get_pc(np_cycle_type, np_tag, np_len, np_addr, np_data);
                    status("Verifying I/O READ completion header");
                    compare(SUCCESSFUL_COMPLETE_DATA_SO, np_cycle_type, "CYCLETYPE");
                    compare(0, np_tag, "TAG");
                    compare(n, np_len, "LENGTH");
                    rdata  = np_data;
                end // if (!completed)

                check_espi_status(16'h030f);
                for (int i = 0; i < n; i++) begin
                    compare(data[i], rdata[i], "Read DATA");
                end
            end // fork begin

            fork
                begin
                    @(posedge mem_read);
                    for (int waits = $urandom_range(0,8<<io_mode); waits > 0; waits--) begin
                        mem_waitrequest = 1;
                        @(posedge clk);
                    end
                    
                    mem_waitrequest = 0;
                    mem_readdata    = 0;
                    b               = 0;
                    for (int i = addr[1:0]; b < n; i++) begin
                        mem_readdata[i*8+:8] = data[b++];
                    end
                    @(posedge clk);

                    status("Verifying I/O READ operation");
                    compare({addr[31:2], 2'b0}, mem_address, "Memory Address");
                end // fork begin
                begin
                    #1e6;
                    local_error_cnt++;
                    status("Memory Read TIMEOUT");
                end
            join_any
        join

        disable fork;
        status("Verifying I/O READ data");
        for (int i = addr[1:0]; i < n; i++) begin
            compare(data[i], rdata[i], "Memory Read Data");
        end
    endtask // put_memrd_short_trial
        

    task put_pc_memwr_trial();
        logic [31:0] addr;
        logic [11:0] length;
        logic [7:0]  data[];
        logic [31:0] address;
        logic [31:0] writedata;
        logic [3:0]  byteenable;
        int          n;
        int          dws;
        int          n_first;
        int          n_last;
        logic [3:0]  be_first;
        logic [3:0]  be_last;
        int          b;
        localparam N_MPS = $clog2(PC_MAX_PAYLOAD_SIZE);

        // Choose an address and length that ensures that a pc_max_payload_size boundary is not crossed.
        n                = $urandom_range(1,PC_MAX_PAYLOAD_SIZE);
        addr             = $urandom();
        addr[N_MPS-1:0]  = n == PC_MAX_PAYLOAD_SIZE ? 0 : $urandom_range(0,PC_MAX_PAYLOAD_SIZE-n);
        statusf("Writing %1d bytes to Memory address %08x", {argf(n), argf(addr)});
        data = new[n];
        for (int i = 0; i < n; i++) begin
            data[i] = $urandom()%2**8;
        end

        n_first  = 4-addr[1:0];
        case (n)
          1: be_first       = 4'b0001<<addr[1:0];
          2: be_first       = 4'b0011<<addr[1:0];
          3: be_first       = 4'b0111<<addr[1:0];
          default: be_first = 4'b1111<<addr[1:0];
        endcase // case (n)

        if (n <= 4) begin
            n_last  = 0;
            be_last = 0;
        end
        else begin
            n_last = (n-n_first)%4;
            if (n_last == 0) begin
                n_last  = 4;
                be_last = 4'b1111;
            end
            else begin
                be_last = 4'b1111>>(4-n_last);
            end
        end // else: !if(n <= 4)

        dws        = (n_first+3)/4+(n-(n_first+n_last))/4+(n_last+3)/4;
        b          = 0;
        address    = {addr[31:2], 2'b0};
        writedata  = 0;
        byteenable = 0;
        fork
            begin
                espi_master_bfm.put_memwr(0, addr, 0, data);
            end

            fork
                begin
                    for (int dw=0; dw<dws; dw++) begin
                        @(posedge mem_write);
                        @(posedge clk);
                        if (dw == 0) begin
                            byteenable       = be_first;
                            writedata[7:0]   = byteenable[0] ? data[b++] : 0;
                            writedata[15:8]  = byteenable[1] ? data[b++] : 0;
                            writedata[23:16] = byteenable[2] ? data[b++] : 0;
                            writedata[31:24] = byteenable[3] ? data[b++] : 0;
                        end
                        else if (n > 4 && dw == dws-1) begin
                            byteenable       = be_last;
                            writedata[7:0]   = byteenable[0] ? data[b++] : 0;
                            writedata[15:8]  = byteenable[1] ? data[b++] : 0;
                            writedata[23:16] = byteenable[2] ? data[b++] : 0;
                            writedata[31:24] = byteenable[3] ? data[b++] : 0;
                        end
                        else begin
                            byteenable       = 4'b1111;
                            writedata[7:0]   = data[b++];
                            writedata[15:8]  = data[b++];
                            writedata[23:16] = data[b++];
                            writedata[31:24] = data[b++];
                        end // else: !if(n > 4 && dw == dws-1)
                        
                        status("Verifying MEMORY WRITE operation");
                        compare(address+4*dw, mem_address, "Memory Address");
                        compare(byteenable, mem_byteenable, "Memory Write Byte Enable");
                        compare(writedata, mem_writedata, "Memory Write Data");
                    end // for (int dw=0; dw<dws; dw++)
                end // fork begin

                begin
                    #1e7;
                    local_error_cnt++;
                    status("Memory Write TIMEOUT");
                end
            join_any

        join
        disable fork;
    endtask // put_pc_memwr_trial


    task put_np_memrd_trial();
        logic        deferred;
        logic [7:0]  cycle_type;
        logic [3:0]  response_tag;
        logic [11:0] response_length;
        logic [63:0] response_addr;
        logic [7:0]  response_data[];
        logic [3:0]  tag;
        logic [11:0] length;
        logic [31:0] addr;
        logic [7:0]  data[];
        int          n;
        int          b;
        int          tries;
        localparam N_MPS = $clog2(PC_MAX_PAYLOAD_SIZE);

        
        // Choose an address and length that ensures that a pc_max_payload_size boundary is not crossed.
        n                = $urandom_range(1,PC_MAX_PAYLOAD_SIZE);
        tag              = $urandom_range(1, 15);
        addr             = $urandom();
        addr[N_MPS-1:0]  = n == PC_MAX_PAYLOAD_SIZE ? 0 : $urandom_range(0,PC_MAX_PAYLOAD_SIZE-n);
        statusf("Reading %1d bytes from Memory address %08x", {argf(n), argf(addr)});
        data = new[n];
        for (int i = 0; i < n; i++) begin
            data[i] = $urandom();
        end

        b = 0;
        fork
            begin
                espi_master_bfm.put_memrd(deferred, cycle_type, response_tag, response_length,
                                          response_data, tag, n, addr, 1'b0);
                if (deferred) begin
                    wait (!espi_alert_n);
                    tries = 0;
                    do begin
                        espi_master_bfm.get_status(bfm_status);
                    end
                    while (!bfm_status[0][PC_AVAIL] && tries++ < 10); // PC_AVAIL
                    espi_master_bfm.get_pc(cycle_type, response_tag, response_length,
                                           response_addr, response_data);
                    compare(SUCCESSFUL_COMPLETE_DATA_SO, cycle_type, "CYCLETYPE");
                end // if (deferred)
                status("Verifying MEMORY READ response header");
                compare(tag, response_tag, "Response TAG");
                compare(n, response_length, "Response LENGTH");
                status("Verifying MEMORY READ data");
                for (int i = 0; i < response_length; i++) begin
                    compare(data[i], response_data[i], "Response DATA");
                end
            end // fork begin

            fork
                begin
                    while (b < n) begin
                        @(posedge mem_read);
                        for (int waits = $urandom_range(0,8<<io_mode); waits > 0; waits--) begin
                            mem_waitrequest = 1;
                            @(posedge clk);
                        end

                        mem_waitrequest = 0;
                        mem_readdata    = 0;
                        for (int i = (addr[1:0]+b)%4; i < 4 && b < n; i++) begin
                            mem_readdata[i*8+:8] = data[b++];
                        end
                    end // while (b < n)
                end // fork begin

                begin
                    #1e7;
                    local_error_cnt++;
                    status("Memory Write TIMEOUT");
                end
            join_any

        join
        disable fork;
    endtask // put_np_memrd_trial
    

    task espi_reg_read(output [7:0] reg_data, input [15:0] addr);
        logic [7:0]  data[];
        logic        completed;
        logic [7:0]  cycle_type;
        logic [3:0]  tag;
        logic [11:0] len;
        logic [63:0] address;
        int          tries;
        logic [7:0]  save_tb_response;

        data                        = new[1];
        save_tb_response            = espi_master_bfm.tb_response;
        espi_master_bfm.tb_response = DEFER;
        espi_master_bfm.put_iord_short(completed, data, addr);
        if (!completed) begin
            wait (!espi_alert_n);
            tries = 0;
            do begin
                espi_master_bfm.get_status(bfm_status);
            end
            while (!bfm_status[0][PC_AVAIL] && tries++ < 10); // PC_AVAIL
            espi_master_bfm.get_pc(cycle_type, tag, len, address, data);
            compare(SUCCESSFUL_COMPLETE_DATA_SO, cycle_type, "CYCLETYPE");
            compare(0, tag, "TAG");
            compare(1, len, "LENGTH");
        end // if (!completed)
        reg_data                    = data[0];
        espi_master_bfm.tb_response = save_tb_response;
    endtask // espi_reg_read

    task espi_reg_write(input [15:0] addr, input [7:0] reg_data);
        logic [7:0]  data[];
        logic        completed;
        logic [7:0]  cycle_type;
        logic [3:0]  tag;
        logic [11:0] len;
        logic [63:0] address;
        int          tries;
        logic [7:0]  save_tb_response;

        data                        = new[1];
        data[0]                     = reg_data;
        save_tb_response            = espi_master_bfm.tb_response;
        espi_master_bfm.tb_response = DEFER;
        espi_master_bfm.put_iowr_short(completed, addr, data);
        if (!completed) begin
            wait (!espi_alert_n);
            tries = 0;
            do begin
                espi_master_bfm.get_status(bfm_status);
            end
            while (!bfm_status[0][PC_AVAIL] && tries++ < 10); // PC_AVAIL
            espi_master_bfm.get_pc(cycle_type, tag, len, address, data);
            compare(SUCCESSFUL_COMPLETE_NO_DATA, cycle_type, "CYCLETYPE");
            compare(0, tag, "TAG");
            if (!(len == 1 || len == 2 || len == 4)) begin
                compare(0, len, "LENGTH in {1, 2, 4}");
            end
        end // if (!completed)
        espi_master_bfm.tb_response = save_tb_response;
    endtask // espi_reg_write

    task espi_config();
        status("Configuring ESPI");
        espi_master_bfm.reset(); 

        espi_master_bfm.get_config(DEVICE_ID_OFFSET, tb_wr_config);
        exp_data = 32'h1;
        compare(exp_data, tb_wr_config, "Device ID");    

        espi_master_bfm.get_config(GENERAL_CONFIG_OFFSET, tb_wr_config);
        exp_data = 32'h0300_0007 | (MAX_OP_FREQ << 16);
        compare(exp_data, tb_wr_config, "General Configuration");

        tb_wr_config = 
                       { 
                         1'b1,   // 31    - CRC Check enable
                         1'b0,   // 30    - Response modifier enabled: 0 slave must not append, 1 ok to append pc/vw/flash to GET_STATUS
                         1'b0,   // 29    - Reserved 
                         1'b0,   // 28    - Alert mode 0-> use IO[1] as alert, 0-> use alert pin
                         io_mode,  // 27:26 - IO mode selected -  single
                         2'b11,  // 25:24 - IO mode support (read only) - set to single/dual/quad
                         1'b0,   // 23    - open drain select, select not supported
                         op_freq, // 22:20 - select operating frequency
                         1'b1,   // 19    - HW init: 0 open drain alert not support, 1 supported 
                         3'b000, // 18:16 - Max freq supported
                         max_wait, // 15:12 - Max wait state allowed
                         8'd0,   // 11:4  - Reserved
                         1'h1,   // 3     - flash supported (read only)
                         1'h1,   // 2     - oob suported (read only)
                         1'h1,   // 1     - vw suported (read only)
                         1'h1    // 0     - pc supported (read only)
                         };
        espi_master_bfm.set_config( GENERAL_CONFIG_OFFSET, tb_wr_config );
        espi_master_bfm.tb_io_mode = io_mode;
        espi_master_bfm.tb_op_freq = op_freq;
        #1;
        espi_master_bfm.get_config(CHANNEL_1_CONFIG_OFFSET, tb_wr_config);
        exp_data = 32'h700; // set to expected return data
        compare(exp_data, tb_wr_config, "VW configuration readback is incorrect");    

        tb_wr_config = 
                       {
                        10'b0,       // Reserved
                        6'b000111,   // Max Virtual wire count
                        2'b0,        // Reserved
                        6'b0,        // RO - max VW count supported
                        6'b0,        // Reserved
                        1'b0,        // RO - VW ready
                        1'b1         // VW enable
                        };
        espi_master_bfm.set_config(CHANNEL_1_CONFIG_OFFSET, tb_wr_config );

        espi_master_bfm.get_config(CHANNEL_2_CONFIG_OFFSET, tb_wr_config);
        exp_data = 32'h110; // set to expected return data
        compare(exp_data, tb_wr_config, "OOB configuration readback is incorrect");    

        tb_wr_config = 
                       {
                        20'b0,       // Reserved
                        3'b001,      // Max OOB payload size selected
                        1'b0,        // Reserved
                        3'b001,      // RO - max OOB payload size supported
                        2'b0,        // Reserved
                        1'b0,        // RO - OOB ready
                        1'b1         // OOB enable
                        };
        espi_master_bfm.set_config(CHANNEL_2_CONFIG_OFFSET, tb_wr_config );
        status("ESPI Configured");
    endtask // espi_config

    task deassert_pltrst_n();
        logic [7:0] data[];
        status("Deasserting PLTRST#");
        data    = new[2];
        data[0] = 8'h03;
        data[1] = 8'h22;
        espi_master_bfm.put_vwire(data);
        status("PLTRST# Deasserted");
    endtask // deassert_pltrst_n

    task check_espi_status(input [15:0] status);
        compare(status, espi_master_bfm.espi_status, "eSPI STATUS");
    endtask // check_espi_status

    task avmm_read_reg(input [4:0] addr, output reg [31:0] rdata);
        $display("%t: %m: Address: %02h", $time, addr);
        @(posedge clk);
        avmm_address   = addr;
        avmm_read      = 1;
        avmm_writedata = 0;
        avmm_write     = 0;
        @(posedge clk);

        avmm_address = 0;
        avmm_read    = 0;
        wait (avmm_readdatavalid);
        @(posedge clk);

        rdata = avmm_readdata;
        $display("%t: %m: Data: %08h", $time, rdata);
    endtask // avmm_read_reg

    task avmm_write_reg(input [4:0] addr, input [31:0] wdata);
        $display("%t: %m: Address: %02h", $time, addr);
        $display("%t: %m: Data: %08h", $time, wdata);
        @(posedge clk);

        avmm_address   = addr;
        avmm_writedata = wdata;
        avmm_write     = 1;
        @(posedge clk);

        avmm_address   = 0;
        avmm_writedata = 0;
        avmm_write     = 0;
    endtask // avmm_write_reg

    task tb_reset();
        @(posedge clk);

        slave_boot_load_done   = 0;
        slave_boot_load_status = 0;
        oob_rst_ack            = 0;
        wake_n                 = 1;
        pme_n                  = 1;
        sci_n                  = 1;
        smi_n                  = 1;
        rcin_n                 = 1;
        host_rst_ack           = 0;
        rsmrst_n               = 1;
        error_nonfatal         = 0;
        error_fatal            = 0;
        mem_readdata           = 0;
        mem_waitrequest        = 0;
        io_readdata            = 0;
        io_waitrequest         = 0;
        reset_n                = 0;
        repeat(50) @(posedge clk);

        reset_n = 1;
        @(posedge clk);
    endtask // tb_reset

endmodule // tb
